package com.aplink.generic.google.maps.clustering.algo;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import android.support.v4.util.LongSparseArray;

import com.aplink.generic.google.maps.clustering.Cluster;
import com.aplink.generic.google.maps.clustering.ClusterItem;
import com.aplink.generic.google.maps.geometry.Point;
import com.aplink.generic.google.maps.projection.SphericalMercatorProjection;

/**
 * Groups markers into a grid.
 */
public class GridBasedAlgorithm<T extends ClusterItem> implements Algorithm<T> {
    private static final int GRID_SIZE = 100;

    private static long getCoord(final long numCells, final double x,
            final double y) {
        return (long) ((numCells * Math.floor(x)) + Math.floor(y));
    }

    private final Set<T> mItems = Collections.synchronizedSet(new HashSet<T>());

    @Override
    public void addItem(final T item) {
        mItems.add(item);
    }

    @Override
    public void addItems(final Collection<T> items) {
        mItems.addAll(items);
    }

    @Override
    public void clearItems() {
        mItems.clear();
    }

    @Override
    public Set<? extends Cluster<T>> getClusters(final double zoom) {
        final long numCells = (long) Math.ceil((256 * Math.pow(2, zoom))
                / GridBasedAlgorithm.GRID_SIZE);
        final SphericalMercatorProjection proj = new SphericalMercatorProjection(
                numCells);

        final HashSet<Cluster<T>> clusters = new HashSet<Cluster<T>>();
        final LongSparseArray<StaticCluster<T>> sparseArray = new LongSparseArray<StaticCluster<T>>();

        synchronized (mItems) {
            for (final T item : mItems) {
                final Point p = proj.toPoint(item.getPosition());

                final long coord = GridBasedAlgorithm.getCoord(numCells, p.x,
                        p.y);

                StaticCluster<T> cluster = sparseArray.get(coord);
                if (cluster == null) {
                    cluster = new StaticCluster<T>(proj.toLatLng(new Point(Math
                            .floor(p.x) + .5, Math.floor(p.y) + .5)));
                    sparseArray.put(coord, cluster);
                    clusters.add(cluster);
                }
                cluster.add(item);
            }
        }

        return clusters;
    }

    @Override
    public Collection<T> getItems() {
        return mItems;
    }

    @Override
    public void removeItem(final T item) {
        mItems.remove(item);
    }
}
